/*
 * Copyright 2008 Max Kugland
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.splink.asbeangen.parser;

import java.util.ArrayList;
import java.util.List;

import org.splink.asbeangen.parser.utils.ClassFileWriter;
import org.splink.asbeangen.parser.utils.CommentMapper;
import org.splink.asbeangen.templates.ITemplateClass;
import org.splink.asbeangen.templates.ITemplateDataClass;
import org.splink.asbeangen.templates.ITemplateReaderClass;

import com.wutka.dtd.DTDItem;
import com.wutka.dtd.DTDPCData;

/**
 * @author maxmc
 */
public class SubelementParser {
	private Parser _parser;
	private CommentMapper _commentMapper;
	private ClassFileWriter _classFileWriter;
	private String _currentSeqCardinal;
	private List<String> _pcDataElements;
	private ArrayList<ITemplateReaderClass> _readerClassTemplates;

	public SubelementParser(Parser parser, CommentMapper commentMapper, List<String> pcDataElements,
			ClassFileWriter classFileWriter) {
		_parser = parser;
		_commentMapper = commentMapper;
		_classFileWriter = classFileWriter;
		_pcDataElements = pcDataElements;

		_readerClassTemplates = new ArrayList<ITemplateReaderClass>();
	}

	protected void parseSubelements(DTDItem item, String classpath, ITemplateDataClass dataClazz,
			ITemplateReaderClass readerClazz) {

		if (_readerClassTemplates.contains(readerClazz)) {
			return;
		}

		_readerClassTemplates.add(readerClazz);

		List<DTDItem> items = getItems(getSequence(item), item, dataClazz, readerClazz);

		for (DTDItem subitem : items) {
			_currentSeqCardinal = "";

			if (_pcDataElements.contains(ParseTools.getName(subitem))) {
				String n = ParseTools.getName(subitem);
				dataClazz.addToMembers(n, "String", ITemplateClass.VAR_TYPE_NATIVE);
				dataClazz.addToConstructor(n, "String", ITemplateClass.VAR_TYPE_NATIVE);
				dataClazz.addToMethods(n, "String", ITemplateClass.VAR_TYPE_NATIVE);

				int cardinal = subitem.cardinal.type;
				readerClazz.addPcDataToReadMethod(n, "String", (cardinal != 0));
				continue;
			}

			if (parseSubSequence(classpath, dataClazz, readerClazz, subitem))
				continue;
			if (parseSubChoice(classpath, dataClazz, readerClazz, subitem))
				continue;

			addValues(subitem, classpath, dataClazz, readerClazz, false);

			_parser.parseElement(subitem, classpath);
		}
	}

	private List<DTDItem> getItems(List<DTDItem> items, DTDItem item, ITemplateDataClass dataClazz,
			ITemplateReaderClass readerClazz) {
		if (items.size() < 1)
			items = ParseTools.getMixed(item);
		if (addPCData(item, dataClazz, readerClazz))
			items = ParseTools.getMixed(item);
		if (items.size() < 1)
			items = ParseTools.getChoice(item);
		return items;
	}

	private boolean parseSubChoice(String classpath, ITemplateDataClass dataclazz, ITemplateReaderClass readerClazz,
			DTDItem subitem) {
		List<DTDItem> choices = ParseTools.getChoice(subitem);
		if (choices.size() > 0) {
			for (DTDItem choice : choices) {
				addValues(choice, classpath, dataclazz, readerClazz, true);
				_parser.parseElement(choice, classpath);
			}
			return true;
		}
		return false;
	}

	private boolean parseSubSequence(String classpath, ITemplateDataClass dataclazz, ITemplateReaderClass packerclazz,
			DTDItem subitem) {
		List<DTDItem> items = getSequence(subitem);
		if (items.size() > 0) {
			for (DTDItem item : items) {
				addValues(item, classpath, dataclazz, packerclazz, false);
				_parser.parseElement(item, classpath);
			}
			return true;
		}
		return false;
	}

	private void addValues(DTDItem item, String classpath, ITemplateDataClass dataClazz,
			ITemplateReaderClass readerClazz, boolean isChoice) {

		if (dataClazz != null) {
			String name = ParseTools.getName(item);
			String cardinal = ParseTools.getCardinal(item);

			if (cardinal.equals(""))
				cardinal = _currentSeqCardinal;
			if (isChoice)
				cardinal = "?";

			String type = _commentMapper.getCustomType(name, cardinal);

			ITemplateClass existingClazz = _classFileWriter.getClazzByName(name);
			if (existingClazz != null)
				classpath = existingClazz.getPackagename() + ".";

			boolean addAccessorImport = addAccessorMethods(name, type, dataClazz, cardinal);

			if (!ParseTools.isArray(cardinal) || addAccessorImport) {
				if (!dataClazz.getID().equals(name)) {
					dataClazz.addToImports(classpath + name);
				}
			}

			dataClazz.addToMembers(name, type, ITemplateClass.VAR_TYPE_CUSTOM);
			dataClazz.addToConstructor(name, type, ITemplateClass.VAR_TYPE_CUSTOM);
			dataClazz.addToMethods(name, type, ITemplateClass.VAR_TYPE_CUSTOM);

			if (ParseTools.isArray(cardinal)) {
				readerClazz.addSubelementArrayToReadMethod(name, (cardinal.equals("?")));
				readerClazz.addSubelementArrayMethod(name);
			} else {
				readerClazz.addSubelementToReadMethod(name, (cardinal.equals("?")));
			}

			if (!readerClazz.getID().equals(name)) {
				readerClazz.addToImports(classpath + name);
			}
		}
	}

	private boolean addAccessorMethods(String name, String type, ITemplateDataClass dataclazz, String cardinal) {
		if (ParseTools.isArray(cardinal)) {
			List<String> attributes = _commentMapper.getGenerate(name);
			if (attributes.size() > 0) {
				for (String attr : attributes) {
					dataclazz.addToAccessMethods(name, attr, _commentMapper.getNativeType(name + "." + attr, ""));
				}
				return true;
			}
		}
		return false;
	}

	private boolean addPCData(DTDItem item, ITemplateDataClass dataclazz, ITemplateReaderClass readerClazz) {
		DTDPCData pcdata = ParseTools.getPcData(item);

		if (pcdata != null) {
			dataclazz.addToMembers("", "", ITemplateClass.VAR_TYPE_PCDATA);
			dataclazz.addToConstructor("", "", ITemplateClass.VAR_TYPE_PCDATA);
			dataclazz.addToMethods("", "", ITemplateClass.VAR_TYPE_PCDATA);

			readerClazz.addContentToReadMethod();

			return true;
		}
		return false;
	}

	private List<DTDItem> getSequence(DTDItem item) {
		List<DTDItem> v = ParseTools.getSequence(item);
		if (v.size() > 0)
			_currentSeqCardinal = ParseTools.getCardinal(item);
		return v;
	}
}